/*
 * Copyright (C) 2013 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.aplink.generic.google.datetimepicker.date;

import java.security.InvalidParameterException;
import java.util.Calendar;
import java.util.Formatter;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;

import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.Paint.Style;
import android.graphics.Rect;
import android.graphics.Typeface;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.support.v4.widget.ExploreByTouchHelper;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
import android.text.format.Time;
import android.view.MotionEvent;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;

import com.aplink.generic.google.datetimepicker.Utils;
import com.aplink.generic.google.datetimepicker.date.MonthAdapter.CalendarDay;
import com.aplink.sg.R;

/**
 * A calendar-like view displaying a specified month and the appropriate
 * selectable day numbers within the specified month.
 */
public abstract class MonthView extends View {
	/**
	 * Provides a virtual view hierarchy for interfacing with an accessibility
	 * service.
	 */
	private class MonthViewTouchHelper extends ExploreByTouchHelper {
		private static final String DATE_FORMAT = "dd MMMM yyyy";

		private final Calendar mTempCalendar = Calendar.getInstance();
		private final Rect mTempRect = new Rect();

		public MonthViewTouchHelper(final View host) {
			super(host);
		}

		public void clearFocusedVirtualView() {
			final int focusedVirtualView = getFocusedVirtualView();
			if (focusedVirtualView != ExploreByTouchHelper.INVALID_ID) {
				this.getAccessibilityNodeProvider(MonthView.this)
				.performAction(
						focusedVirtualView,
						AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS,
						null);
			}
		}

		/**
		 * Calculates the bounding rectangle of a given time object.
		 *
		 * @param day
		 *            The day to calculate bounds for
		 * @param rect
		 *            The rectangle in which to store the bounds
		 */
		private void getItemBounds(final int day, final Rect rect) {
			final int offsetX = MonthView.this.mPadding;
			final int offsetY = MonthView.MONTH_HEADER_SIZE;
			final int cellHeight = MonthView.this.mRowHeight;
			final int cellWidth = ((MonthView.this.mWidth - (2 * MonthView.this.mPadding)) / MonthView.this.mNumDays);
			final int index = ((day - 1) + findDayOffset());
			final int row = (index / MonthView.this.mNumDays);
			final int column = (index % MonthView.this.mNumDays);
			final int x = (offsetX + (column * cellWidth));
			final int y = (offsetY + (row * cellHeight));

			rect.set(x, y, (x + cellWidth), (y + cellHeight));
		}

		/**
		 * Generates a description for a given time object. Since this
		 * description will be spoken, the components are ordered by descending
		 * specificity as DAY MONTH YEAR.
		 *
		 * @param day
		 *            The day to generate a description for
		 * @return A description of the time object
		 */
		private CharSequence getItemDescription(final int day) {
			this.mTempCalendar.set(MonthView.this.mYear, MonthView.this.mMonth,
					day);
			final CharSequence date = DateFormat.format(
					MonthViewTouchHelper.DATE_FORMAT,
					this.mTempCalendar.getTimeInMillis());

			if (day == MonthView.this.mSelectedDay) {
				return getContext().getString(R.string.item_is_selected, date);
			}

			return date;
		}

		@Override
		protected int getVirtualViewAt(final float x, final float y) {
			final int day = getDayFromLocation(x, y);
			if (day >= 0) {
				return day;
			}
			return ExploreByTouchHelper.INVALID_ID;
		}

		@Override
		protected void getVisibleVirtualViews(final List<Integer> virtualViewIds) {
			for (int day = 1; day <= MonthView.this.mNumCells; day++) {
				virtualViewIds.add(day);
			}
		}

		@Override
		protected boolean onPerformActionForVirtualView(
				final int virtualViewId, final int action,
				final Bundle arguments) {
			switch (action) {
				case AccessibilityNodeInfo.ACTION_CLICK:
					onDayClick(virtualViewId);
					return true;
			}

			return false;
		}

		@Override
		protected void onPopulateEventForVirtualView(final int virtualViewId,
				final AccessibilityEvent event) {
			event.setContentDescription(getItemDescription(virtualViewId));
		}

		@TargetApi(Build.VERSION_CODES.JELLY_BEAN)
		@Override
		protected void onPopulateNodeForVirtualView(final int virtualViewId,
				final AccessibilityNodeInfoCompat node) {
			getItemBounds(virtualViewId, this.mTempRect);

			node.setContentDescription(getItemDescription(virtualViewId));
			node.setBoundsInParent(this.mTempRect);
			node.addAction(AccessibilityNodeInfo.ACTION_CLICK);

			if (virtualViewId == MonthView.this.mSelectedDay) {
				node.setSelected(true);
			}

		}

		public void setFocusedVirtualView(final int virtualViewId) {
			this.getAccessibilityNodeProvider(MonthView.this).performAction(
					virtualViewId,
					AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS,
					null);
		}
	}

	/**
	 * Handles callbacks when the user clicks on a time object.
	 */
	public interface OnDayClickListener {
		public void onDayClick(MonthView view, CalendarDay day);
	}

	protected static int DAY_SELECTED_CIRCLE_SIZE;
	protected static int DAY_SEPARATOR_WIDTH = 1;
	protected static final int DEFAULT_FOCUS_MONTH = -1;
	protected static int DEFAULT_HEIGHT = 32;
	protected static final int DEFAULT_NUM_DAYS = 7;
	protected static final int DEFAULT_NUM_ROWS = 6;

	protected static final int DEFAULT_SELECTED_DAY = -1;
	protected static final int DEFAULT_SHOW_WK_NUM = 0;
	protected static final int DEFAULT_WEEK_START = Calendar.SUNDAY;
	protected static final int MAX_NUM_ROWS = 6;
	protected static int MIN_HEIGHT = 10;
	protected static int MINI_DAY_NUMBER_TEXT_SIZE;
	protected static int MONTH_DAY_LABEL_TEXT_SIZE;
	protected static int MONTH_HEADER_SIZE;
	protected static int MONTH_LABEL_TEXT_SIZE;

	// used for scaling to the device density
	protected static float mScale = 0;

	private static final int SELECTED_CIRCLE_ALPHA = 60;
	/**
	 * Which month is currently in focus, as defined by {@link Time#month}
	 * [0-11].
	 */
	public static final String VIEW_PARAMS_FOCUS_MONTH = "focus_month";
	/**
	 * These params can be passed into the view to control how it appears.
	 * {@link #VIEW_PARAMS_WEEK} is the only required field, though the default
	 * values are unlikely to fit most layouts correctly.
	 */
	/**
	 * This sets the height of this week in pixels
	 */
	public static final String VIEW_PARAMS_HEIGHT = "height";
	/**
	 * This specifies the position (or weeks since the epoch) of this week,
	 * calculated using {@link Utils#getWeeksSinceEpochFromJulianDay}
	 */
	public static final String VIEW_PARAMS_MONTH = "month";
	/**
	 * How many days to display at a time. Days will be displayed starting with
	 * {@link #mWeekStart}.
	 */
	public static final String VIEW_PARAMS_NUM_DAYS = "num_days";
	/**
	 * This sets one of the days in this view as selected {@link Time#SUNDAY}
	 * through {@link Time#SATURDAY}.
	 */
	public static final String VIEW_PARAMS_SELECTED_DAY = "selected_day";

	/**
	 * If this month should display week numbers. false if 0, true otherwise.
	 */
	public static final String VIEW_PARAMS_SHOW_WK_NUM = "show_wk_num";

	/**
	 * Which day the week should start on. {@link Time#SUNDAY} through
	 * {@link Time#SATURDAY}.
	 */
	public static final String VIEW_PARAMS_WEEK_START = "week_start";

	/**
	 * This specifies the position (or weeks since the epoch) of this week,
	 * calculated using {@link Utils#getWeeksSinceEpochFromJulianDay}
	 */
	public static final String VIEW_PARAMS_YEAR = "year";
	private final Calendar mCalendar;

	private final Calendar mDayLabelCalendar;
	private int mDayOfWeekStart = 0;
	private final String mDayOfWeekTypeface;
	protected int mDayTextColor;
	// The Julian day of the first day displayed by this item
	protected int mFirstJulianDay = -1;

	// The month of the first day in this week
	protected int mFirstMonth = -1;
	private final Formatter mFormatter;

	// If this view contains the today
	protected boolean mHasToday = false;
	// The month of the last day in this week
	protected int mLastMonth = -1;
	// Whether to prevent setting the accessibility delegate
	private final boolean mLockAccessibilityDelegate;

	protected int mMonth;

	protected Paint mMonthDayLabelPaint;
	protected Paint mMonthNumPaint;
	protected int mMonthTitleBGColor;
	protected Paint mMonthTitleBGPaint;
	protected int mMonthTitleColor;
	protected Paint mMonthTitlePaint;
	private final String mMonthTitleTypeface;
	// The number of days + a spot for week number if it is displayed
	protected int mNumCells = this.mNumDays;
	// How many days to display
	protected int mNumDays = MonthView.DEFAULT_NUM_DAYS;
	private int mNumRows = MonthView.DEFAULT_NUM_ROWS;
	// Optional listener for handling day click actions
	private OnDayClickListener mOnDayClickListener;

	// affects the padding on the sides of this view
	protected int mPadding = 0;
	// The height this view should draw at in pixels, set by height param
	protected int mRowHeight = MonthView.DEFAULT_HEIGHT;
	protected Paint mSelectedCirclePaint;

	// Which day is selected [0-6] or -1 if no day is selected
	protected int mSelectedDay = -1;

	// The left edge of the selected day
	protected int mSelectedLeft = -1;
	// The right edge of the selected day
	protected int mSelectedRight = -1;

	private final StringBuilder mStringBuilder;
	// Which day is today [0-6] or -1 if no day is today
	protected int mToday = MonthView.DEFAULT_SELECTED_DAY;
	protected int mTodayNumberColor;
	private final MonthViewTouchHelper mTouchHelper;

	// Which day of the week to start on [0-6]
	protected int mWeekStart = MonthView.DEFAULT_WEEK_START;

	// Quick reference to the width of this view, matches parent
	protected int mWidth;

	protected int mYear;

	public MonthView(final Context context) {
		super(context);

		final Resources res = context.getResources();

		this.mDayLabelCalendar = Calendar.getInstance();
		this.mCalendar = Calendar.getInstance();

		this.mDayOfWeekTypeface = res
				.getString(R.string.day_of_week_label_typeface);
		this.mMonthTitleTypeface = res.getString(R.string.sans_serif);

		this.mDayTextColor = res.getColor(R.color.date_picker_text_normal);
		this.mTodayNumberColor = res.getColor(R.color.blue);
		this.mMonthTitleColor = res.getColor(R.color.white);
		this.mMonthTitleBGColor = res.getColor(R.color.circle_background);

		this.mStringBuilder = new StringBuilder(50);
		this.mFormatter = new Formatter(this.mStringBuilder,
				Locale.getDefault());

		MonthView.MINI_DAY_NUMBER_TEXT_SIZE = res
				.getDimensionPixelSize(R.dimen.day_number_size);
		MonthView.MONTH_LABEL_TEXT_SIZE = res
				.getDimensionPixelSize(R.dimen.month_label_size);
		MonthView.MONTH_DAY_LABEL_TEXT_SIZE = res
				.getDimensionPixelSize(R.dimen.month_day_label_text_size);
		MonthView.MONTH_HEADER_SIZE = res
				.getDimensionPixelOffset(R.dimen.month_list_item_header_height);
		MonthView.DAY_SELECTED_CIRCLE_SIZE = res
				.getDimensionPixelSize(R.dimen.day_number_select_circle_radius);

		this.mRowHeight = (res
				.getDimensionPixelOffset(R.dimen.date_picker_view_animator_height) - MonthView.MONTH_HEADER_SIZE)
				/ MonthView.MAX_NUM_ROWS;

		// Set up accessibility components.
		this.mTouchHelper = new MonthViewTouchHelper(this);
		ViewCompat.setAccessibilityDelegate(this, this.mTouchHelper);
		ViewCompat.setImportantForAccessibility(this,
				ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
		this.mLockAccessibilityDelegate = true;

		// Sets up any standard paints that will be used
		initView();
	}

	private int calculateNumRows() {
		final int offset = findDayOffset();
		final int dividend = (offset + this.mNumCells) / this.mNumDays;
		final int remainder = (offset + this.mNumCells) % this.mNumDays;
		return (dividend + (remainder > 0 ? 1 : 0));
	}

	/**
	 * Clears accessibility focus within the view. No-op if the view does not
	 * contain accessibility focus.
	 */
	public void clearAccessibilityFocus() {
		this.mTouchHelper.clearFocusedVirtualView();
	}

	@Override
	public boolean dispatchHoverEvent(final MotionEvent event) {
		// First right-of-refusal goes the touch exploration helper.
		if (this.mTouchHelper.dispatchHoverEvent(event)) {
			return true;
		}
		return super.dispatchHoverEvent(event);
	}

	/**
	 * This method should draw the month day. Implemented by sub-classes to
	 * allow customization.
	 *
	 * @param canvas
	 *            The canvas to draw on
	 * @param year
	 *            The year of this month day
	 * @param month
	 *            The month of this month day
	 * @param day
	 *            The day number of this month day
	 * @param x
	 *            The default x position to draw the day number
	 * @param y
	 *            The default y position to draw the day number
	 * @param startX
	 *            The left boundary of the day number rect
	 * @param stopX
	 *            The right boundary of the day number rect
	 * @param startY
	 *            The top boundary of the day number rect
	 * @param stopY
	 *            The bottom boundary of the day number rect
	 */
	public abstract void drawMonthDay(Canvas canvas, int year, int month,
			int day, int x, int y, int startX, int stopX, int startY, int stopY);

	private void drawMonthDayLabels(final Canvas canvas) {
		final int y = MonthView.MONTH_HEADER_SIZE
				- (MonthView.MONTH_DAY_LABEL_TEXT_SIZE / 2);
		final int dayWidthHalf = (this.mWidth - (this.mPadding * 2))
				/ (this.mNumDays * 2);

		for (int i = 0; i < this.mNumDays; i++) {
			final int calendarDay = (i + this.mWeekStart) % this.mNumDays;
			final int x = (((2 * i) + 1) * dayWidthHalf) + this.mPadding;
			this.mDayLabelCalendar.set(Calendar.DAY_OF_WEEK, calendarDay);
			canvas.drawText(
					this.mDayLabelCalendar.getDisplayName(Calendar.DAY_OF_WEEK,
							Calendar.SHORT, Locale.getDefault()).toUpperCase(
									Locale.getDefault()), x, y,
									this.mMonthDayLabelPaint);
		}
	}

	/**
	 * Draws the week and month day numbers for this week. Override this method
	 * if you need different placement.
	 *
	 * @param canvas
	 *            The canvas to draw on
	 */
	protected void drawMonthNums(final Canvas canvas) {
		int y = (((this.mRowHeight + MonthView.MINI_DAY_NUMBER_TEXT_SIZE) / 2) - MonthView.DAY_SEPARATOR_WIDTH)
				+ MonthView.MONTH_HEADER_SIZE;
		final int dayWidthHalf = (this.mWidth - (this.mPadding * 2))
				/ (this.mNumDays * 2);
		int j = findDayOffset();
		for (int dayNumber = 1; dayNumber <= this.mNumCells; dayNumber++) {
			final int x = (((2 * j) + 1) * dayWidthHalf) + this.mPadding;

			final int yRelativeToDay = ((this.mRowHeight + MonthView.MINI_DAY_NUMBER_TEXT_SIZE) / 2)
					- MonthView.DAY_SEPARATOR_WIDTH;

			final int startX = x - dayWidthHalf;
			final int stopX = x + dayWidthHalf;
			final int startY = y - yRelativeToDay;
			final int stopY = startY + this.mRowHeight;

			drawMonthDay(canvas, this.mYear, this.mMonth, dayNumber, x, y,
					startX, stopX, startY, stopY);

			j++;
			if (j == this.mNumDays) {
				j = 0;
				y += this.mRowHeight;
			}
		}
	}

	private void drawMonthTitle(final Canvas canvas) {
		final int x = (this.mWidth + (2 * this.mPadding)) / 2;
		final int y = ((MonthView.MONTH_HEADER_SIZE - MonthView.MONTH_DAY_LABEL_TEXT_SIZE) / 2)
				+ (MonthView.MONTH_LABEL_TEXT_SIZE / 3);
		canvas.drawText(getMonthAndYearString(), x, y, this.mMonthTitlePaint);
	}

	private int findDayOffset() {
		return (this.mDayOfWeekStart < this.mWeekStart ? (this.mDayOfWeekStart + this.mNumDays)
				: this.mDayOfWeekStart)
				- this.mWeekStart;
	}

	/**
	 * @return The date that has accessibility focus, or {@code null} if no date
	 *         has focus
	 */
	public CalendarDay getAccessibilityFocus() {
		final int day = this.mTouchHelper.getFocusedVirtualView();
		if (day >= 0) {
			return new CalendarDay(this.mYear, this.mMonth, day);
		}
		return null;
	}

	/**
	 * Calculates the day that the given x position is in, accounting for week
	 * number. Returns the day or -1 if the position wasn't in a day.
	 *
	 * @param x
	 *            The x position of the touch event
	 * @return The day number, or -1 if the position wasn't in a day
	 */
	public int getDayFromLocation(final float x, final float y) {
		final int dayStart = this.mPadding;
		if ((x < dayStart) || (x > (this.mWidth - this.mPadding))) {
			return -1;
		}
		// Selection is (x - start) / (pixels/day) == (x -s) * day / pixels
		final int row = (int) (y - MonthView.MONTH_HEADER_SIZE)
				/ this.mRowHeight;
		final int column = (int) (((x - dayStart) * this.mNumDays) / (this.mWidth
				- dayStart - this.mPadding));

		int day = (column - findDayOffset()) + 1;
		day += row * this.mNumDays;
		if ((day < 1) || (day > this.mNumCells)) {
			return -1;
		}
		return day;
	}

	private String getMonthAndYearString() {
		final int flags = DateUtils.FORMAT_SHOW_DATE
				| DateUtils.FORMAT_SHOW_YEAR | DateUtils.FORMAT_NO_MONTH_DAY;
		this.mStringBuilder.setLength(0);
		final long millis = this.mCalendar.getTimeInMillis();
		return DateUtils.formatDateRange(getContext(), this.mFormatter, millis,
				millis, flags, Time.getCurrentTimezone()).toString();
	}

	/**
	 * Sets up the text and style properties for painting. Override this if you
	 * want to use a different paint.
	 */
	protected void initView() {
		this.mMonthTitlePaint = new Paint();
		this.mMonthTitlePaint.setFakeBoldText(true);
		this.mMonthTitlePaint.setAntiAlias(true);
		this.mMonthTitlePaint.setTextSize(MonthView.MONTH_LABEL_TEXT_SIZE);
		this.mMonthTitlePaint.setTypeface(Typeface.create(
				this.mMonthTitleTypeface, Typeface.BOLD));
		this.mMonthTitlePaint.setColor(this.mDayTextColor);
		this.mMonthTitlePaint.setTextAlign(Align.CENTER);
		this.mMonthTitlePaint.setStyle(Style.FILL);

		this.mMonthTitleBGPaint = new Paint();
		this.mMonthTitleBGPaint.setFakeBoldText(true);
		this.mMonthTitleBGPaint.setAntiAlias(true);
		this.mMonthTitleBGPaint.setColor(this.mMonthTitleBGColor);
		this.mMonthTitleBGPaint.setTextAlign(Align.CENTER);
		this.mMonthTitleBGPaint.setStyle(Style.FILL);

		this.mSelectedCirclePaint = new Paint();
		this.mSelectedCirclePaint.setFakeBoldText(true);
		this.mSelectedCirclePaint.setAntiAlias(true);
		this.mSelectedCirclePaint.setColor(this.mTodayNumberColor);
		this.mSelectedCirclePaint.setTextAlign(Align.CENTER);
		this.mSelectedCirclePaint.setStyle(Style.FILL);
		this.mSelectedCirclePaint.setAlpha(MonthView.SELECTED_CIRCLE_ALPHA);

		this.mMonthDayLabelPaint = new Paint();
		this.mMonthDayLabelPaint.setAntiAlias(true);
		this.mMonthDayLabelPaint
		.setTextSize(MonthView.MONTH_DAY_LABEL_TEXT_SIZE);
		this.mMonthDayLabelPaint.setColor(this.mDayTextColor);
		this.mMonthDayLabelPaint.setTypeface(Typeface.create(
				this.mDayOfWeekTypeface, Typeface.NORMAL));
		this.mMonthDayLabelPaint.setStyle(Style.FILL);
		this.mMonthDayLabelPaint.setTextAlign(Align.CENTER);
		this.mMonthDayLabelPaint.setFakeBoldText(true);

		this.mMonthNumPaint = new Paint();
		this.mMonthNumPaint.setAntiAlias(true);
		this.mMonthNumPaint.setTextSize(MonthView.MINI_DAY_NUMBER_TEXT_SIZE);
		this.mMonthNumPaint.setStyle(Style.FILL);
		this.mMonthNumPaint.setTextAlign(Align.CENTER);
		this.mMonthNumPaint.setFakeBoldText(false);
	}

	/**
	 * Called when the user clicks on a day. Handles callbacks to the
	 * {@link OnDayClickListener} if one is set.
	 *
	 * @param day
	 *            The day that was clicked
	 */
	private void onDayClick(final int day) {
		if (this.mOnDayClickListener != null) {
			this.mOnDayClickListener.onDayClick(this, new CalendarDay(
					this.mYear, this.mMonth, day));
		}

		// This is a no-op if accessibility is turned off.
		this.mTouchHelper.sendEventForVirtualView(day,
				AccessibilityEvent.TYPE_VIEW_CLICKED);
	}

	@Override
	protected void onDraw(final Canvas canvas) {
		drawMonthTitle(canvas);
		drawMonthDayLabels(canvas);
		drawMonthNums(canvas);
	}

	@Override
	protected void onMeasure(final int widthMeasureSpec,
			final int heightMeasureSpec) {
		setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),
				(this.mRowHeight * this.mNumRows) + MonthView.MONTH_HEADER_SIZE);
	}

	@Override
	protected void onSizeChanged(final int w, final int h, final int oldw,
			final int oldh) {
		this.mWidth = w;

		// Invalidate cached accessibility information.
		this.mTouchHelper.invalidateRoot();
	}

	@Override
	public boolean onTouchEvent(final MotionEvent event) {
		switch (event.getAction()) {
			case MotionEvent.ACTION_UP:
				final int day = getDayFromLocation(event.getX(), event.getY());
				if (day >= 0) {
					onDayClick(day);
				}
				break;
		}
		return true;
	}

	/**
	 * Attempts to restore accessibility focus to the specified date.
	 *
	 * @param day
	 *            The date which should receive focus
	 * @return {@code false} if the date is not valid for this month view, or
	 *         {@code true} if the date received focus
	 */
	public boolean restoreAccessibilityFocus(final CalendarDay day) {
		if ((day.year != this.mYear) || (day.month != this.mMonth)
				|| (day.day > this.mNumCells)) {
			return false;
		}
		this.mTouchHelper.setFocusedVirtualView(day.day);
		return true;
	}

	public void reuse() {
		this.mNumRows = MonthView.DEFAULT_NUM_ROWS;
		requestLayout();
	}

	private boolean sameDay(final int day, final Time today) {
		return (this.mYear == today.year) && (this.mMonth == today.month)
				&& (day == today.monthDay);
	}

	@Override
	public void setAccessibilityDelegate(final AccessibilityDelegate delegate) {
		// Workaround for a JB MR1 issue where accessibility delegates on
		// top-level ListView items are overwritten.
		if (!this.mLockAccessibilityDelegate) {
			super.setAccessibilityDelegate(delegate);
		}
	}

	/**
	 * Sets all the parameters for displaying this week. The only required
	 * parameter is the week number. Other parameters have a default value and
	 * will only update if a new value is included, except for focus month,
	 * which will always default to no focus month if no value is passed in. See
	 * {@link #VIEW_PARAMS_HEIGHT} for more info on parameters.
	 *
	 * @param params
	 *            A map of the new parameters, see {@link #VIEW_PARAMS_HEIGHT}
	 */
	public void setMonthParams(final HashMap<String, Integer> params) {
		if (!params.containsKey(MonthView.VIEW_PARAMS_MONTH)
				&& !params.containsKey(MonthView.VIEW_PARAMS_YEAR)) {
			throw new InvalidParameterException(
					"You must specify month and year for this view");
		}
		this.setTag(params);
		// We keep the current value for any params not present
		if (params.containsKey(MonthView.VIEW_PARAMS_HEIGHT)) {
			this.mRowHeight = params.get(MonthView.VIEW_PARAMS_HEIGHT);
			if (this.mRowHeight < MonthView.MIN_HEIGHT) {
				this.mRowHeight = MonthView.MIN_HEIGHT;
			}
		}
		if (params.containsKey(MonthView.VIEW_PARAMS_SELECTED_DAY)) {
			this.mSelectedDay = params.get(MonthView.VIEW_PARAMS_SELECTED_DAY);
		}

		// Allocate space for caching the day numbers and focus values
		this.mMonth = params.get(MonthView.VIEW_PARAMS_MONTH);
		this.mYear = params.get(MonthView.VIEW_PARAMS_YEAR);

		// Figure out what day today is
		final Time today = new Time(Time.getCurrentTimezone());
		today.setToNow();
		this.mHasToday = false;
		this.mToday = -1;

		this.mCalendar.set(Calendar.MONTH, this.mMonth);
		this.mCalendar.set(Calendar.YEAR, this.mYear);
		this.mCalendar.set(Calendar.DAY_OF_MONTH, 1);
		this.mDayOfWeekStart = this.mCalendar.get(Calendar.DAY_OF_WEEK);

		if (params.containsKey(MonthView.VIEW_PARAMS_WEEK_START)) {
			this.mWeekStart = params.get(MonthView.VIEW_PARAMS_WEEK_START);
		} else {
			this.mWeekStart = this.mCalendar.getFirstDayOfWeek();
		}

		this.mNumCells = Utils.getDaysInMonth(this.mMonth, this.mYear);
		for (int i = 0; i < this.mNumCells; i++) {
			final int day = i + 1;
			if (sameDay(day, today)) {
				this.mHasToday = true;
				this.mToday = day;
			}
		}
		this.mNumRows = calculateNumRows();

		// Invalidate cached accessibility information.
		this.mTouchHelper.invalidateRoot();
	}

	public void setOnDayClickListener(final OnDayClickListener listener) {
		this.mOnDayClickListener = listener;
	}
}
